package com.zy.explore.client.infrastructure.plugins.mybatis.sqltransform;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import com.zy.explore.client.infrastructure.plugins.util.SqlPrintUtil;
import com.zy.explore.client.infrastructure.util.PropertiesUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ReuseExecutor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;


/**
 * <p>建类说明：自定义sql拦截器</p>
 * 这种方式虽简单，但难以打印sql执行时间
 * @author 中国印
 * @date 2022年02月17日 15:12:35
 */
@Slf4j
public class CustomInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {

    private static final List<String> ENV_LIST = Arrays.asList("local", "dev");

    /**
     * 判断是否执行
     * <p>
     * 如果不执行query操作,则返回false
     *
     * @param executor      Executor(可能是代理对象)
     * @param ms            MappedStatement
     * @param parameter     parameter
     * @param rowBounds     rowBounds
     * @param resultHandler resultHandler
     * @param boundSql      boundSql
     * @return 新的 boundSql
     */
    @Override
    public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        return InnerInterceptor.super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    }

    /**
     * 查询时处理逻辑
     *
     * 改改sql啥的
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        // //通过 JSqlParser工具修改查询sql后执行
        // String originSql = mpBs.sql().replaceAll("[\\s]+", " ");
        // System.out.println("原始MySQL语句:" + originSql + "  参数：" + (parameter != null ? JSON.toJSONString(parameter) : "无"));
        //
        // // 打印完整sql语句
        // String wholeSql = printAndGetFormattedSql(ms, boundSql, 0);
        // System.out.println("完整SQL: " + wholeSql);
        //
        // // 解析sql，如在sql的字段上增加 ``
        // String sql = parserSingle(originSql, boundSql);
        // System.out.println("解析之后的MySQL: " + sql);
        //
        // // 模拟改写后的sql
        // // String targetSql = "SELECT * FROM example WHERE id = 1";
        // // 根据项目使用的数据库类型，解析sql，转换成对应数据库的sql语言
        // String targetSql = MySqlToOracleUtil.transformMysqlToOracle(sql);
        // // String targetSql = "SELECT * FROM \"example\"";
        // System.out.println("实际执行的SQL语句：" + targetSql);
        // mpBs.sql(targetSql);
    }

    /**
     * 判断是否执行 {@link Executor#update(MappedStatement, Object)}
     *
     * 增删改操作，该方法执行顺序为1
     *
     * 如果不执行update操作,则影响行数的值为 -1
     *
     * @param executor  Executor(可能是代理对象)
     * @param ms        MappedStatement
     * @param parameter parameter
     */
    @Override
    public boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        return InnerInterceptor.super.willDoUpdate(executor, ms, parameter);
    }

    /**
     * {@link Executor#update(MappedStatement, Object)} 操作前置处理
     *
     * 增删改操作，该方法执行顺序为2
     *
     * 改改sql啥的
     *
     * @param executor  Executor(可能是代理对象)
     * @param ms        MappedStatement
     * @param parameter parameter
     */
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {

        // InnerInterceptor.super.beforeUpdate(executor, ms, parameter);
        //
        // BoundSql boundSql = ms.getBoundSql(parameter);
        // PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        // //通过 JSqlParser工具修改查询sql后执行
        // String originSql = mpBs.sql().replaceAll("[\\s]+", " ");
        // System.out.println("原始MySQL语句:" + originSql + "  参数：" + (parameter != null ? JSON.toJSONString(parameter) : "无"));
        //
        // // 打印完整sql语句
        // String wholeSql = printAndGetFormattedSql(ms, boundSql, 0);
        // System.out.println("完整SQL: " + wholeSql);
        //
        // // 解析sql，如在sql的字段上增加 ``
        // String sql = parserSingle(originSql, boundSql);
        // System.out.println("解析之后的MySQL: " + sql);

        // // 模拟改写后的sql
        // // String targetSql = "SELECT * FROM example WHERE id = 1";
        // // 根据项目使用的数据库类型，解析sql，转换成对应数据库的sql语言
        // String targetSql = MySqlToOracleUtil.transformMysqlToOracle(sql);
        // // String targetSql = "SELECT * FROM \"example\"";
        // System.out.println("实际执行的SQL语句：" + targetSql);
        // mpBs.sql(targetSql);

    }

    /**
     * 增删改时 处理逻辑
     *
     * 增删改操作，该方法执行顺序为3
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();

        // 增删改调用 JSqlParser工具修改sql后执行
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), null));
        }
    }

    /**
     * {@link StatementHandler#getBoundSql()} 操作前置处理
     * <p>
     * 只有 {@link BatchExecutor} 和 {@link ReuseExecutor} 才会调用到这个方法
     *
     * @param sh StatementHandler(可能是代理对象)
     */
    @Override
    public void beforeGetBoundSql(StatementHandler sh) {
        InnerInterceptor.super.beforeGetBoundSql(sh);
    }

    @Override
    public void setProperties(Properties properties) {
        InnerInterceptor.super.setProperties(properties);
    }


    /**
     * 以处理查询sql为例,增删改的实现可根据自己的业务实现
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        //此处处理select逻辑 将字段拼接上 ``
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            reformatPlainSelect((PlainSelect) selectBody);
        }
    }


    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        List<Column> columns = insert.getColumns();
        insert.setColumns(disposeInsertColumn(columns));
    }

    /**
     * 方法说明：处理插入列
     *
     * @param columns 列
     * @return {@link List<Column> }
     */
    private List<Column> disposeInsertColumn(List<Column> columns) {
        return columns.stream().map(this::resetInsertColumn).collect(Collectors.toList());
    }

    private Column resetInsertColumn(Column column) {
        return new Column(appendPrefixAndSuffix(column.getColumnName().toUpperCase()));
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        System.out.println("删除前调用,可修改sql");
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        System.out.println("修改调用,可修改sql");
    }


    //---------------以下为处理sql操作,根据自己业务功能完善


    /**
     * 处理查询字段
     */
    private List<SelectItem> disposeSelectColumn(List<SelectItem> selectItems) {
        return selectItems.stream().map(this::resetSelectItem).collect(Collectors.toList());
    }

    private SelectItem resetSelectItem(SelectItem selectItem) {
        //如果不符合直接返回
        if (!(selectItem instanceof SelectExpressionItem)) {
            return selectItem;
        }
        SelectExpressionItem item = (SelectExpressionItem) selectItem;
        //如果是列
        if (item.getExpression() instanceof Column) {
            Column columnExp = (Column) item.getExpression();
            return new SelectExpressionItem(reFormatSelectColumn(columnExp, item.getAlias()));
        }

        //如果是函数
        if (item.getExpression() instanceof Function) {
            Function function = (Function) item.getExpression();
            return new SelectExpressionItem(reFormatFunction(function));
        }
        return item;
    }


    /**
     * 重新格式化 查询语句
     *
     * @param plainSelect 查询语句
     */
    public void reformatPlainSelect(PlainSelect plainSelect) {

        // 要查询的字段集合
        List<SelectItem> selectItems = plainSelect.getSelectItems();

        // 表名对象
        FromItem fromItem = plainSelect.getFromItem();
        plainSelect.setFromItem(new Table(fromItem.toString().toUpperCase()));

        // 处理查询条件
        plainSelect.setSelectItems(disposeSelectColumn(selectItems));

        // 处理 where 条件
        plainSelect.setWhere(disposeSelectWhere(plainSelect.getWhere()));

        // 处理 order by

        // 处理 group by

        // 处理 PlainSelect 对象中的其他属性 ...

    }


    /**
     * 重新格式化列
     *
     * @param columnExp 列
     * @param alias     列别名
     * @return 格式化的列
     */
    private Column reFormatSelectColumn(Column columnExp, Alias alias) {
        if (columnExp == null) {
            return null;
        }

        //表名和列簇名会在一起
        String tableAndCfName = columnExp.getTable() == null ? "" : columnExp.getTable().toString();
        // 字段名
        String columnName = columnExp.getColumnName();


        // 根据 `.` 分隔方便处理表名和列簇名
        String[] tableAndCfInfo = tableAndCfName.split("\\.");

        // 可能会出现很多情况 列名  列簇.列名  表名.列簇.列名 表名.列名
        String tableName = tableAndCfInfo[0];
        String cf = tableAndCfInfo[tableAndCfInfo.length - 1];

        // 如果表名和字段名相等,只有3种情况: 列名  表名.列名  列簇.列名
        if (StrUtil.equals(tableName, cf) && StrUtil.isNotBlank(tableName)) {
            // 判断前缀是表名还是列名  要求列簇必须全大写 表名不能全大写
            // 如果全大写这是列簇名
            if (StrUtil.equals(cf.toUpperCase(), cf)) {
                tableName = "";
            } else {
                //否则是表名
                cf = "";
            }
        }

        StringBuilder finalName = new StringBuilder();

        // 如果表名不为空 拼接表名
        if (StrUtil.isNotBlank(tableName)) {
            finalName.append(tableName).append(".");
        }
        //如果列簇名不为空 拼接列簇名
        if (StrUtil.isNotBlank(cf)) {
            finalName.append(appendPrefixAndSuffix(cf)).append(".");
        }
        // 拼接字段名
        finalName.append(appendPrefixAndSuffix(columnName));
        // 拼接别名: as xxx
        if (alias != null) {
            finalName.append(" ").append(alias.getName());
        }

        //重新格式化列名 封装返回
        return new Column(finalName.toString().toUpperCase());
    }

    /**
     * 重新格式化查询函数
     *
     * @param function 函数
     * @return 格式化的函数
     */
    private Function reFormatFunction(Function function) {

        List<Expression> expressions = function.getParameters().getExpressions();

        //对于是列的参数进行格式化
        expressions = expressions.stream().map(exp -> {
            if (exp instanceof Column) {
                return reFormatSelectColumn((Column) exp, null);
            }
            return exp;
        }).collect(Collectors.toList());

        //重新设置回去
        function.getParameters().setExpressions(expressions);

        return function;
    }


    /**
     * 重新格式化子查询
     *
     * @param subSelect 子查询
     */
    private void reFormatSubSelect(SubSelect subSelect) {
        if (subSelect.getSelectBody() instanceof PlainSelect) {
            reformatPlainSelect((PlainSelect) subSelect.getSelectBody());
        }
    }


    public Expression disposeSelectWhere(Expression expression) {

        if (!(expression instanceof BinaryExpression)) {
            return expression;
        }

        BinaryExpression binaryExpression = (BinaryExpression) expression;

        // 如果左边还是多条件的
        if (binaryExpression.getLeftExpression() instanceof BinaryExpression) {
            disposeSelectWhere(binaryExpression.getLeftExpression());
        }

        // 如果右边还是多条件的
        if (binaryExpression.getRightExpression() instanceof BinaryExpression) {
            disposeSelectWhere(binaryExpression.getRightExpression());
        }

        // 如果左边表达式是列信息 格式化
        if (binaryExpression.getLeftExpression() instanceof Column) {
            Column newColumn = reFormatSelectColumn((Column) binaryExpression.getLeftExpression(), null);
            binaryExpression.setLeftExpression(newColumn);
        }

        // 如果左边表达式是 子查询 processPlainSelect
        if (binaryExpression.getLeftExpression() instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) binaryExpression.getLeftExpression();
            if (subSelect.getSelectBody() instanceof PlainSelect) {
                reformatPlainSelect((PlainSelect) subSelect.getSelectBody());
            }
        }

        // 如果右边是列信息 格式化
        if (binaryExpression.getRightExpression() instanceof Column) {
            Column newColumn = reFormatSelectColumn((Column) binaryExpression.getLeftExpression(), null);
            binaryExpression.setRightExpression(newColumn);
        }

        // 如果右边表达式是 子查询 processPlainSelect
        if (binaryExpression.getRightExpression() instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) binaryExpression.getRightExpression();
            reFormatSubSelect(subSelect);
        }
        return binaryExpression;
    }


    /**
     * <p>方法说明：添加前缀和后缀</p>
     *
     * @param str str
     * @return {@link String }
     */
    private String appendPrefixAndSuffix(String str) {

        String dbUrl = PropertiesUtil.getProperty("spring.datasource.url", "");
        String dbType = JdbcUtils.getDbType(dbUrl).getDb();
        // 默认mysql的前后缀
        String prefix = "`";
        String suffix = "`";

        if (DbType.DM.getDb().equals(dbType)) {
            prefix = "\"";
            suffix = "\"";
        }
        // 如果已经有前缀了直接返回
        if (str.contains(prefix)) {
            return str;
        }
        // 拼接前缀和后缀
        return prefix + str + suffix;
    }

    /**
     * <p>方法说明：打印格式化sql</p>
     *
     * @param ms       映射对象
     * @param boundSql 绑定sql
     */
    private String printAndGetFormattedSql(MappedStatement ms, BoundSql boundSql, long time) {
        Configuration configuration = ms.getConfiguration();
        String sqlId = ms.getId();
        String wholeSql = SqlPrintUtil.fillSqlParams(configuration, boundSql);
        // 这里可以控制打印耗时sql (非开发环境耗时超过2秒就打印sql，开发环境总是打印sql)
        if (time == 0 || time > 2000 || ENV_LIST.contains(PropertiesUtil.getProperty("spring.profiles.active", "prod"))) {
            System.err.println("【SQL_ID】=>" + sqlId + "; " + (time == 0 ? "" : "【耗时】=>" + time + "ms;") + "【SQL】=>" + wholeSql + ";");
            log.error(wholeSql);
        }
        return wholeSql;
    }
}
